package com.quantum.gc
{
	import com.pktech.flexutil.ProfilingManager;
	import com.pktech.flexutil.configuration.ConfigurationUtil;
	import com.pktech.flexutil.logging.LogManager;

	import flash.events.TimerEvent;
	import flash.net.LocalConnection;
	import flash.system.System;
	import flash.utils.Timer;
	import flash.utils.getTimer;

	import mx.core.FlexGlobals;
	import mx.core.UIComponent;
	import mx.logging.ILogger;

	public class GarbageCollector
	{
		public static const DEFAULT_INTERVAL:int=30000;
		private static const MIN_TIME_BETWEEN_2_GC:int=5000;
		private static var _logger:ILogger=LogManager.getLogger(GarbageCollector);

		//---------------------------------------------------------------------------
		// Class Properties

		//-------------------------------------------------------
		// instance
		private static var _instance:GarbageCollector;

		public static function get instance():GarbageCollector
		{
			if (!_instance)
			{
				_instance=new GarbageCollector(PrivateConstructorToken);
			}
			return _instance;
		}

		public function GarbageCollector(token:Class)
		{
			if (token != PrivateConstructorToken)
			{
				throw new Error("GarbageCollector cannot be instantiated directly.");
			}
			if (this.periodicGCEnabled)
			{
				_timer=new Timer(DEFAULT_INTERVAL);
				_timer.addEventListener(TimerEvent.TIMER, handleTimer);
			}

			_gcPendingTimer=new Timer(0, 1);
			_gcPendingTimer.addEventListener(TimerEvent.TIMER, pendingGCTimerHandler);
		}

		public function initialize(interval:int=DEFAULT_INTERVAL):void
		{
			this.interval=interval;
		}


		//---------------------------------------------------------------------------
		// Instance Variables

		/**
		 * A boolean value that specifies if it is a forced GC (somewhere from the application)
		 * or called from the end of the timer cycle.
		 */
		private var _gcPending:Boolean=false;
		private var _timer:Timer;
		private var _gcPendingTimer:Timer;
		private var _interval:int=0;
		private var _lastGCTime:int;
		private var _pendingExtern:Boolean=false;

		/**
		 * The system's total memory after the last garbage collection.
		 */
		[Bindable]
		public var totalMemory:Number=System.totalMemory;

		//---------------------------------------------------------------------------
		// Instance Properties

		/**
		 * Returns true if the Periodic Garbage Collector is enabled
		 */
		public function get periodicGCEnabled():Boolean
		{
			return ConfigurationUtil.getValueAsBoolean("periodicGC", false);
		}

		//-------------------------------------------------------
		// interval

		/**
		 * At what intervals the garbage collection process should run.
		 */
		public function get interval():int
		{
			return _interval;
		}

		public function set interval(value:int):void
		{
			if (_interval != value)
			{
				_interval=value;
				if (_timer == null)
				{
					return;
				}

				_timer.stop();
				_timer.delay=_interval;
				if (_interval > 0)
				{
					_timer.start();
				}
			}
		}

		//---------------------------------------------------------------------------
		// Instance Methods

		/**
		 * Forces the garbage collection process.
		 */
		public function gc():void
		{
			doRealGC(true);
		}

		/**
		 * Force a GC on the next flash cycle.
		 */
		internal function gcLater():void
		{
			var app:UIComponent=UIComponent(FlexGlobals.topLevelApplication);
			app.callLater(gcInternal);
		}

		private function gcInternal():void
		{
			doRealGC(true);
		}

		private function doPendingGC(pendingTime:int, externalRequest:Boolean):void
		{
			if (!_gcPending)
			{
				_gcPending=true;
				_pendingExtern=externalRequest;
				_gcPendingTimer.delay=pendingTime;
				_gcPendingTimer.start();
			}
		}

		private function doRealGC(externalRequest:Boolean):void
		{
			//It is not possible to manually invoke gc in Flash.
			//A hack to invoke the gc was found and published in the year 2006. The hack was ...
			//LocalConnection().connect("foo");
			//LocalConnection().connect("foo");
			//call LocalConnection.connect twice. This throws an error ... #2082 Already connected (on the 2nd one).
			//LocalConnection is Flash package class hence the source code is not available
			//but internally it must be invoking the garbage collector when connect fails.
			//We modified it a little bit by using only 1 LocalConnection.
			//The 1st call to this method will not invoke the gc.
			//The 2nd call (and all subsequent calls) to this method will invoke the gc.
			//if the user has a debugger version of Flash plugin then we invioke the System.gc()
			//which is available in the debugger version only.

			//we do gc if ...
			//allowManualGC=true : If developers want to manually invoke gc.
			//AND last gc was invoked atleast 10 seconds before

			//if the gc() is invoked in short intervals then do not gc, make it pending.
			var diff:int=getTimer() - _lastGCTime;
			if (diff < MIN_TIME_BETWEEN_2_GC)
			{
				doPendingGC(MIN_TIME_BETWEEN_2_GC - diff, externalRequest);
				return;
			}

			if (externalRequest)
			{
				ProfilingManager.instance.cumulativeOperationStarted(_logger, "gc");
			}
			_logger.info("Garbage Collection Invoked");
			try
			{
				//hack urls: http://www.gskinner.com/blog/archives/2006/08/as3_resource_ma_2.html
				new LocalConnection().connect("foo");
				new LocalConnection().connect("foo");
			}
			catch (e:Error)
			{
				//do not handle error #2082 because the above code will fail and throw an error #2082
				//every time gc happens because the connect fails.
				if (!(e is ArgumentError) || (e as ArgumentError).errorID != 2082)
				{
					trace("Unexpected error during garbage collection: " + e.message);
				}
			}
			if (externalRequest)
			{
				ProfilingManager.instance.cumulativeOperationEnded(_logger, "gc");
				restartTimer();
			}
			this.totalMemory=System.totalMemory;
			_lastGCTime=getTimer();
			_gcPending=false;
		}

		/**
		 * Restart the periodic timer.
		 */
		private function restartTimer():void
		{
			if (_timer == null)
			{
				return;
			}
			if (_interval > 0)
			{
				_timer.reset();
				_timer.start();
			}
		}

		private function handleTimer(evt:TimerEvent):void
		{
			doRealGC(false);
		}

		private function pendingGCTimerHandler(evt:TimerEvent):void
		{
			if (_gcPending)
			{
				doRealGC(_pendingExtern);
			}
		}
	}

}

// http://livedocs.adobe.com/flex/3/html/03_Language_and_Syntax_05.html#151506
class PrivateConstructorToken
{
}